/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.kernel;

import java.util.Arrays;
import java.util.Map;

/**
 * This abstract class defines the basic elements common to all code runners.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.2
 * @since 1.0
 */
public abstract class AbstractCodeRunner implements CodeRunner {

    /**
     * Context, that is program-specific elements
     * (as multiple programs can be ran within the same JVM).
     */
    protected final Context context;

    /**
     * Closure to be executed
     * (<tt>null</tt> if execution from code start).
     */
    protected Value closure;

    /**
     * Arguments to closure to be executed
     * (ignored if closure is <tt>null</tt>).
     */
    protected Value[] args;


    /** Thread status. */
    protected Value threadStatus;

    /** Script bindings. */
    protected Map<String, Value> bindings;

    /**
     * Constructs a code runner from a context.
     * @param context context for code runner - should not be <tt>null</tt>
     */
    AbstractCodeRunner(final Context context) {
        assert context != null : "null context";
        this.context = context;
        this.closure = null;
        this.args = null;
    } // end constructor(Context)

    /**
     * {@inheritDoc}
     */
    @Override
    public final Context getContext() {
        return this.context;
    } // end method 'getContext()'

    /**
     * {@inheritDoc}
     */
    @Override
    public final boolean isNative() {
        return this.context.isNative();
    } // end method 'isNative()'

    /**
     * {@inheritDoc}
     */
    @Override
    public final void setBindings(final Map<String, Value> b) {
        this.bindings = b;
    } // end method 'setBindings(Map<String, Value>)'

    /**
     * {@inheritDoc}
     */
    @Override
    public final Map<String, Value> getBindings() {
        return this.bindings;
    } // end method 'getBindings()'

    /**
     * {@inheritDoc}
     */
    @Override
    public final void setup(final Value closure, final Value... args) {
        this.closure = closure;
        if (args != null) {
            this.args = Arrays.copyOf(args, args.length);
        } else {
            this.args = null;
        } // end if/else
    } // end method 'setup(Value, Value...)'

    /**
     * {@inheritDoc}
     */
    @Override
    public final Value getThreadStatus() {
        return this.threadStatus;
    } // end method 'getThreadStatus()'

    /**
     * {@inheritDoc}
     */
    @Override
    public final void setThreadStatus(final Value ts) {
        assert ts != null : "null ts";
        this.threadStatus = ts;
    } // end method 'setThreadStatus(Value)'

    /**
     * {@inheritDoc}
     */
    @Override
    public final Value callback(final String function, final Value... params)
        throws Fail.Exception, Fatal.Exception, FalseExit, CadmiumException {
        assert function != null : "null function";
        assert params != null : "null params";
        assert params.length + 4 <= 256 : "params is too long";
        final Value closure = this.context.getCallback(function);
        if (closure == null) {
            throw new CadmiumException("unknown callback");
        } // end if
        return callback(closure, params);
    } // end method 'callback(String, Value...)'

    /**
     * Return a method of an object.
     * @param obj object to get method from - should not be <tt>null</tt>
     * @param tag method tag, as a raw value
     * @return the method with given tag from given object
     */
    public static Value getMethod(final Value obj, final int tag) {
        final Block meths = obj.asBlock().get(0).asBlock();
        int li = 3;
        int hi = meths.get(0).getRawValue();
        int mi;
        while (li < hi) {
            mi = ((li + hi) >> 1) | 1;
            if (tag < meths.get(mi).getRawValue()) {
                hi = mi - 2;
            } else {
                li = mi;
            } // end if/else
        } // end while
        return (tag == meths.get(li).getRawValue()) ? meths.get(li - 1) : Value.ZERO;
    } // end method 'getMethod(Value, int)'

} // end class 'AbstractCodeRunner'
